package com.zws.cucumber.expander;

import com.zws.cucumber.util.colorfullog.ZwsColorfulLogHelper;
import com.zws.cucumber.var.ZwsVar;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.checkArgument;
import static com.zws.cucumber.expander.ZwsStrFunctionUtil.registerFunction;
import static com.zws.cucumber.expander.ZwsStrInternalUtil.getJsonPath;

/**
 * author: zws
 */
public class ZwsStrBuiltInFunctions {

    private static final String True = "true";
    private static final String False = "false";

    private static final String invalidArgumentsNumber = "should have %d arguments";
    private static final String invalidArgumentsNumberAtLeast = "should have at least %d arguments";
    private static final String invalidArgumentsNumberRange = "should have arguments between %d and %d";

    private static final String invalidArgumentType = "%dth argument should be instance of type '%s'";
    private static final String invalidArgumentTypeRange = "%dth argument should be instance of one of types %s";

    private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private static ConcurrentMap<String, String> resourceMap = new ConcurrentHashMap<>();

    public static void init() {
        // deprecated
        registerFunction("jsonstr", ZwsStrBuiltInFunctions::jsonStr);
        registerFunction("txlogstr", ZwsStrBuiltInFunctions::strTxlog);

        // string
        registerFunction("str", ZwsStrBuiltInFunctions::str);
        registerFunction("str.read", ZwsStrBuiltInFunctions::strRead);
        registerFunction("str.brace_left", ZwsStrBuiltInFunctions::strBraceLeft);
        registerFunction("str.brace_right", ZwsStrBuiltInFunctions::strBraceRight);
        registerFunction("str.comma", ZwsStrBuiltInFunctions::strComma);
        registerFunction("str.empty", ZwsStrBuiltInFunctions::strEmpty);
        registerFunction("str.blank", ZwsStrBuiltInFunctions::strBlank);
        registerFunction("str.trim", ZwsStrBuiltInFunctions::strTrim);
        registerFunction("str.txlog", ZwsStrBuiltInFunctions::strTxlog);
        registerFunction("str.upper", ZwsStrBuiltInFunctions::strUpper);
        registerFunction("str.lower", ZwsStrBuiltInFunctions::strLower);
        registerFunction("str.quote", ZwsStrBuiltInFunctions::strQuote);
        registerFunction("str.unquote", ZwsStrBuiltInFunctions::strUnquote);
        registerFunction("str.join", ZwsStrBuiltInFunctions::strJoin);
        registerFunction("str.if", ZwsStrBuiltInFunctions::strIfThen);
        registerFunction("str.if_then", ZwsStrBuiltInFunctions::strIfThen);
        registerFunction("str.if_empty_then", ZwsStrBuiltInFunctions::strIfEmptyThen);
        registerFunction("str.if_not_empty_then", ZwsStrBuiltInFunctions::strIfNotEmptyThen);
        registerFunction("str.if_else", ZwsStrBuiltInFunctions::strIfThenElse);
        registerFunction("str.if_then_else", ZwsStrBuiltInFunctions::strIfThenElse);
        registerFunction("str.is_empty", ZwsStrBuiltInFunctions::strIsEmpty);
        registerFunction("str.is_not_empty", ZwsStrBuiltInFunctions::strIsNotEmpty);
        registerFunction("str.is_blank", ZwsStrBuiltInFunctions::strIsBlank);
        registerFunction("str.is_not_blank", ZwsStrBuiltInFunctions::strIsNotBlank);
        registerFunction("str.equals", ZwsStrBuiltInFunctions::strEquals);
        registerFunction("str.not_equals", ZwsStrBuiltInFunctions::strNotEquals);
        registerFunction("str.equals_ignore_case", ZwsStrBuiltInFunctions::strEqualsIgnoreCase);
        registerFunction("str.contains", ZwsStrBuiltInFunctions::strContains);
        registerFunction("str.contains_ignore_case", ZwsStrBuiltInFunctions::strContainsIgnoreCase);
        registerFunction("str.matches", ZwsStrBuiltInFunctions::strMatches);
        registerFunction("str.replace", ZwsStrBuiltInFunctions::strReplace);
        registerFunction("str.starts_with", ZwsStrBuiltInFunctions::strStartsWith);
        registerFunction("str.ends_with", ZwsStrBuiltInFunctions::strEndsWith);
        // abbr. for str
        registerFunction("read", ZwsStrBuiltInFunctions::strRead);
        registerFunction("bl", ZwsStrBuiltInFunctions::strBraceLeft); // brace_left
        registerFunction("br", ZwsStrBuiltInFunctions::strBraceRight); // brace_right
        registerFunction("comma", ZwsStrBuiltInFunctions::strComma);
        registerFunction("empty", ZwsStrBuiltInFunctions::strEmpty);
        registerFunction("blank", ZwsStrBuiltInFunctions::strBlank);
        registerFunction("trim", ZwsStrBuiltInFunctions::strTrim);
        registerFunction("upper", ZwsStrBuiltInFunctions::strUpper);
        registerFunction("lower", ZwsStrBuiltInFunctions::strLower);
        registerFunction("quote", ZwsStrBuiltInFunctions::strQuote);
        registerFunction("unquote", ZwsStrBuiltInFunctions::strUnquote);
        registerFunction("join", ZwsStrBuiltInFunctions::strJoin);
        registerFunction("if", ZwsStrBuiltInFunctions::strIfThen);
        registerFunction("if_then", ZwsStrBuiltInFunctions::strIfThen);
        registerFunction("if_else", ZwsStrBuiltInFunctions::strIfThenElse);
        registerFunction("if_then_else", ZwsStrBuiltInFunctions::strIfThenElse);
        registerFunction("if_empty", ZwsStrBuiltInFunctions::strIfEmptyThen);
        registerFunction("if_empty_else", ZwsStrBuiltInFunctions::strIfEmptyThenElse);
        registerFunction("if_not_empty", ZwsStrBuiltInFunctions::strIfNotEmptyThen);
        registerFunction("if_not_empty_else", ZwsStrBuiltInFunctions::strIfNotEmptyThenElse);
        registerFunction("is_empty", ZwsStrBuiltInFunctions::strIsEmpty);
        registerFunction("is_not_empty", ZwsStrBuiltInFunctions::strIsNotEmpty);
        registerFunction("is_blank", ZwsStrBuiltInFunctions::strIsBlank);
        registerFunction("is_not_blank", ZwsStrBuiltInFunctions::strIsNotBlank);
        registerFunction("is_number", ZwsStrBuiltInFunctions::strIsNumber);
        registerFunction("equals", ZwsStrBuiltInFunctions::strEquals);
        registerFunction("not_equals", ZwsStrBuiltInFunctions::strNotEquals);
        registerFunction("eq", ZwsStrBuiltInFunctions::strEquals);
        registerFunction("not_eq", ZwsStrBuiltInFunctions::strNotEquals);

        // logic
        registerFunction("logic.true", ZwsStrBuiltInFunctions::logicTrue);
        registerFunction("logic.false", ZwsStrBuiltInFunctions::logicFalse);
        registerFunction("logic.not", ZwsStrBuiltInFunctions::logicNot);
        registerFunction("logic.and", ZwsStrBuiltInFunctions::logicAnd);
        registerFunction("logic.or", ZwsStrBuiltInFunctions::logicOr);
        registerFunction("true", ZwsStrBuiltInFunctions::logicTrue);
        registerFunction("false", ZwsStrBuiltInFunctions::logicFalse);
        registerFunction("not", ZwsStrBuiltInFunctions::logicNot);
        registerFunction("and", ZwsStrBuiltInFunctions::logicAnd);
        registerFunction("or", ZwsStrBuiltInFunctions::logicOr);

        // time
        registerFunction("time.now_ms", ZwsStrBuiltInFunctions::timeNowMs);
        registerFunction("time.tomorrow_ms", ZwsStrBuiltInFunctions::timeTomorrowMs);
        registerFunction("time.now_s", ZwsStrBuiltInFunctions::timeNowS);

        // json
        registerFunction("json.empty_map", ZwsStrBuiltInFunctions::jsonEmptyMap);
        registerFunction("json.render_prop", ZwsStrBuiltInFunctions::jsonRenderProp);
        registerFunction("json.prop", ZwsStrBuiltInFunctions::jsonRenderProp);
        registerFunction("json.prop2", ZwsStrBuiltInFunctions::jsonRenderPropAtLast);
        registerFunction("json.render_prop_at_last", ZwsStrBuiltInFunctions::jsonRenderPropAtLast);
        registerFunction("json.prop_at_last", ZwsStrBuiltInFunctions::jsonRenderPropAtLast);
        registerFunction("json.prop_join", ZwsStrBuiltInFunctions::jsonRenderPropJoin);
        registerFunction("json.value", ZwsStrBuiltInFunctions::jsonValue);
        registerFunction("json.draw", ZwsStrBuiltInFunctions::jsonDraw);
        registerFunction("json.str", ZwsStrBuiltInFunctions::jsonStr);

        // txlog
        registerFunction("txlog.value", ZwsStrBuiltInFunctions::txlogValue);

        // base64
        registerFunction("base64.enc", ZwsStrBuiltInFunctions::base64Enc);
        registerFunction("base64.dec", ZwsStrBuiltInFunctions::base64Dec);
        registerFunction("base64.enc_urlsafe", ZwsStrBuiltInFunctions::base64EncUrlSafe);
        registerFunction("base64.dec_urlsafe", ZwsStrBuiltInFunctions::base64DecUrlSafe);

        // url
        registerFunction("url.enc", ZwsStrBuiltInFunctions::urlEnc);
        registerFunction("url.dec", ZwsStrBuiltInFunctions::urlDec);

        // http resp
        registerFunction("resp.status", ZwsStrBuiltInFunctions::respStatus);
        registerFunction("resp.draw_body", ZwsStrBuiltInFunctions::respDrawBody);
        registerFunction("resp.draw_header", ZwsStrBuiltInFunctions::respDrawHeader);

        // variable
        registerFunction("get", ZwsStrBuiltInFunctions::get);

        // uuid
        registerFunction("uuid", ZwsStrBuiltInFunctions::uuid);
        registerFunction("uuid_no_dash", ZwsStrBuiltInFunctions::uuidNoDash);
    }

    private static <T> T[] array(T ... array) {
        return array;
    }



    /*
     * common
     */
    private static class ErrorMessage {
        private String format;
        private String functionName;
        private Object[] args;
        public ErrorMessage(String format, String functionName, Object[] args) {
            this.format = format;
            this.functionName = functionName;
            this.args = args;
        }
        @Override
        public String toString() { return String.format("function '" + functionName + "' " + format, args); }
    }

    private static Object errorMessage(String format, String functionName, Object ... args) {
        return new ErrorMessage(format, functionName, args);
    }

    private static String typeName(Class<?> ... classes) {
        if (classes.length == 1) {
            return classes[0].getName();
        } else {
            return Arrays.stream(classes)
                    .map(Class::getName)
                    .collect(Collectors.joining(", ", "[", "]"));
        }
    }

    private static boolean isTrue(Object actual) {
        if (actual instanceof Boolean) {
            return (boolean) actual;

        } else if (actual instanceof String) {
            return True.equalsIgnoreCase((String) actual);

        } else {
            return false;

        }
    }

    private static boolean isFalse(Object actual) {
        if (actual instanceof Boolean) {
            return (boolean) actual;

        } else if (actual instanceof String) {
            return False.equalsIgnoreCase((String) actual);

        } else {
            return true;

        }
    }

    /*
     * str
     */
    private static String str(Boolean debug, Object[] a) {
        if (debug) {
//            checkArgument(a.length == 1,
//                    errorMessage(invalidArgumentsNumber, "str.read", 1));
//            checkArgument(a[0] instanceof String,
//                    errorMessage(invalidArgumentType, "str.read", 0, typeName(String.class)));
        }
        try {
            Object raw = a[0];
            if (raw instanceof byte[] bytes) {
                if (a.length == 1) {
                    return IOUtils.toString(bytes, StandardCharsets.UTF_8.toString());
                } else {
                    return IOUtils.toString(bytes, (String) a[1]);
                }
            }
            if (raw instanceof Number num) {
                return num.toString();
            }
            return raw.toString();
        } catch (Exception ex) {
            if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new RuntimeException(ex);
        }
    }


    private static String strRead(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.read", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.read", 0, typeName(String.class)));
        }
        String pattern = ((String) a[0]).trim();
        return resourceMap.computeIfAbsent(pattern, p -> {
            try {
                Resource[] resources = resourcePatternResolver.getResources(pattern);
                if (resources.length != 1) {
                    throw new IllegalArgumentException(
                            "the number of resources for pattern should be just one, but was %d, found resources: %s".formatted(
                                    resources.length,
                                    Stream.of(resources)
                                            .map(r -> {
                                                try {
                                                    return r.getURL().toString();
                                                } catch (IOException ex) {
                                                    throw new RuntimeException(ex);
                                                }
                                            })
                                            .toList()));
                }
                return IOUtils.toString(resources[0].getInputStream(), StandardCharsets.UTF_8);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });
    }

    private static String strBraceLeft(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "str.brace_left", 0));
        }
        return "{";
    }

    private static String strBraceRight(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "str.brace_right", 0));
        }
        return "}";
    }

    private static String strComma(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "str.comma", 0));
        }
        return ",";
    }

    private static String strEmpty(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "str.empty", 0));
        }
        return "";
    }

    private static String strBlank(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length < 2,
                    errorMessage(invalidArgumentsNumberRange, "str.blank", 0, 1));
            if (a.length == 1) {
                checkArgument(a[0] instanceof String,
                        errorMessage(invalidArgumentType, "str.blank", 0, typeName(String.class)));
            }
        }
        return a.length == 0 ? " " : ZwsColorfulLogHelper.fill(' ', Integer.parseInt((String) a[0]));
    }

    private static String strTrim(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.trim", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.trim", 0, typeName(String.class)));
        }
        return ((String) a[0]).trim();
    }

    private static String strTxlog(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.txlog", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.txlog", 0, typeName(String.class)));
        }
        return StringUtils.replaceEach((String) a[0], array(",", "\"", "\r", "\n"), array(";", "", "", ""));
    }

    private static String strUpper(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.upper", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.upper", 0, typeName(String.class)));
        }
        return ((String) a[0]).toUpperCase();
    }

    private static String strReplace(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 3,
                errorMessage(invalidArgumentsNumber, "str.replace", 3));
            checkArgument(a[0] instanceof String,
                errorMessage(invalidArgumentType, "str.replace", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                errorMessage(invalidArgumentType, "str.replace", 1, typeName(String.class)));
            checkArgument(a[2] instanceof String,
                errorMessage(invalidArgumentType, "str.replace", 2, typeName(String.class)));
        }

        return ((String) a[0]).replace((String)a[1], (String)a[2]);
    }

    private static String strLower(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.lower", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.lower", 0, typeName(String.class)));
        }
        return ((String) a[0]).toLowerCase();
    }

    private static String strQuote(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.quote", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.quote", 0, typeName(String.class)));
        }
        return "\"" + a[0] + "\"";
    }

    private static String strUnquote(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.unquote", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.unquote", 0, typeName(String.class)));
        }
        String s = ((String) a[0]).trim();
        if (s.startsWith("\"") && s.endsWith("\"")) {
            return s.substring(1, s.length() - 1);
        }
        return s;
    }

    private static String strJoin(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length >= 1,
                    errorMessage(invalidArgumentsNumberAtLeast, "str.join", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.join", 0, typeName(String.class)));
        }
        String delimiter = (String) a[0];
        String[] sa = Stream.of(Arrays.copyOfRange(a, 1, a.length))
                .map(o -> (String) o)
                .toArray(String[]::new);
        return String.join(delimiter, sa);
    }

    private static Object strIsEmpty(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.is_empty", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.is_empty", 0, typeName(String.class)));
        }
        return StringUtils.isEmpty((String) a[0]);
    }

    private static Object strIsNotEmpty(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.is_not_empty", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.is_not_empty", 0, typeName(String.class)));
        }
        return StringUtils.isNotEmpty((String) a[0]);
    }

    private static Object strIsBlank(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.is_blank", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.is_blank", 0, typeName(String.class)));
        }
        return StringUtils.isBlank((String) a[0]);
    }

    private static Object strIsNotBlank(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.is_not_blank", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.is_not_blank", 0, typeName(String.class)));
        }
        return StringUtils.isNotBlank((String) a[0]);
    }

    private static Object strIsNumber(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "str.is_number", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.is_number", 0, typeName(String.class)));
        }
        return NumberUtils.isNumber((String) a[0]);
    }

    private static Object strEquals(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.equals", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.equals", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.equals", 1, typeName(String.class)));
        }
        return StringUtils.equals((String) a[0], (String) a[1]);
    }

    private static Object strNotEquals(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.not_equals", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.not_equals", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.not_equals", 1, typeName(String.class)));
        }
        return !StringUtils.equals((String) a[0], (String) a[1]);
    }

    private static Object strEqualsIgnoreCase(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.equals_ignore_case", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.equals_ignore_case", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.equals_ignore_case", 1, typeName(String.class)));
        }
        return StringUtils.equalsIgnoreCase((String) a[0], (String) a[1]);
    }

    private static Object strContains(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.contains", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.contains", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.contains", 1, typeName(String.class)));
        }
        return StringUtils.contains((String) a[0], (String) a[1]);
    }

    private static Object strContainsIgnoreCase(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.contains_ignore_case", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.contains_ignore_case", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.contains_ignore_case", 1, typeName(String.class)));
        }
        return StringUtils.containsIgnoreCase((String) a[0], (String) a[1]);
    }

    private static Object strMatches(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.matches", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.matches", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.matches", 1, typeName(String.class)));
        }
        return ((String) a[0]).matches((String) a[1]);
    }

    private static Object strStartsWith(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.starts_with", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.starts_with", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.starts_with", 1, typeName(String.class)));
        }
        return ((String) a[0]).startsWith((String) a[1]);
    }

    private static Object strEndsWith(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.ends_with", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "str.ends_with", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.ends_with", 1, typeName(String.class)));
        }
        return ((String) a[0]).endsWith((String) a[1]);
    }

    private static String strIfThen(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "str.if_then", 2));
            checkArgument(a[0] instanceof String || a[0] instanceof Boolean,
                    errorMessage(invalidArgumentTypeRange, "str.if_then", 0, typeName(String.class, Boolean.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.if_then", 1, typeName(String.class)));
        }
        String val = (String) a[1];
        if (a[0] instanceof String && StringUtils.isNotBlank((String) a[0])) {
            return val;
        }
        if (a[0] instanceof Boolean && (Boolean) a[0]) {
            return val;
        }
        return "";
    }

    private static String strIfEmptyThen(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "if_empty", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "if_empty", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "if_empty", 1, typeName(String.class)));
        }
        String val = (String) a[1];
        if (a[0] instanceof String cond && StringUtils.isEmpty(cond)) {
            return val;
        }
        return "";
    }

    private static String strIfEmptyThenElse(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 3,
                    errorMessage(invalidArgumentsNumber, "if_empty", 3));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "if_empty", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "if_empty", 1, typeName(String.class)));
            checkArgument(a[2] instanceof String,
                    errorMessage(invalidArgumentType, "if_empty", 2, typeName(String.class)));
        }
        String val1 = (String) a[1];
        String val2 = (String) a[2];
        if (a[0] instanceof String cond && StringUtils.isEmpty(cond)) {
            return val1;
        }
        return val2;
    }

    private static String strIfNotEmptyThen(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2,
                    errorMessage(invalidArgumentsNumber, "if_not_empty", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "if_not_empty", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "if_not_empty", 1, typeName(String.class)));
        }
        String val = (String) a[1];
        if (a[0] instanceof String cond && StringUtils.isNotEmpty(cond)) {
            return val;
        }
        return "";
    }

    private static String strIfNotEmptyThenElse(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 3,
                    errorMessage(invalidArgumentsNumber, "if_not_empty_else", 3));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "if_not_empty_else", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "if_not_empty_else", 1, typeName(String.class)));
            checkArgument(a[2] instanceof String,
                    errorMessage(invalidArgumentType, "if_not_empty_else", 2, typeName(String.class)));
        }
        String val1 = (String) a[1];
        String val2 = (String) a[2];
        if (a[0] instanceof String cond && StringUtils.isNotEmpty(cond)) {
            return val1;
        }
        return val2;
    }

    private static String strIfThenElse(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 3,
                    errorMessage(invalidArgumentsNumber, "str.if_then_else", 3));
            checkArgument(a[0] instanceof String || a[0] instanceof Boolean,
                    errorMessage(invalidArgumentTypeRange, "str.if_then_else", 1, typeName(String.class, Boolean.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "str.if_then_else", 1, typeName(String.class)));
            checkArgument(a[2] instanceof String,
                    errorMessage(invalidArgumentType, "str.if_then_else", 2, typeName(String.class)));
        }
        String val1 = (String) a[1], val2 = (String) a[2];
        if (a[0] instanceof String) {
            if (StringUtils.isNotBlank((String) a[0])) {
                return val1;
            } else {
                return val2;
            }
        }
        if ((Boolean) a[0]) {
            return val1;
        } else {
            return val2;
        }
    }

    /*
     * logic
     */
    private static Object logicTrue(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "logic.true", 0));
        }
        return true;
    }

    private static Object logicFalse(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "logic.false", 0));
        }
        return false;
    }

    private static Object logicNot(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "logic.not", 1));
            checkArgument(a[0] instanceof Boolean,
                    errorMessage(invalidArgumentType, "logic.not", 0, typeName(Boolean.class)));
        }
        return !((Boolean) a[0]);
    }

    private static Object logicAnd(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length >= 2,
                    errorMessage(invalidArgumentsNumberAtLeast, "logic.and", 2));
            for (int i = 0; i < a.length; i++) {
                checkArgument(a[i] instanceof Boolean,
                        errorMessage(invalidArgumentType, "logic.and", i, typeName(Boolean.class)));
            }
        }
        boolean flag = ((Boolean) a[0]) && ((Boolean) a[1]);
        for (int i = 2; i < a.length; i++) {
            flag &= (Boolean) a[i];
        }
        return flag;
    }

    private static Object logicOr(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length >= 2,
                    errorMessage(invalidArgumentsNumberAtLeast, "logic.or", 2));
            for (int i = 0; i < a.length; i++) {
                checkArgument(a[i] instanceof Boolean,
                        errorMessage(invalidArgumentType, "logic.or", i, typeName(Boolean.class)));
            }
        }
        boolean flag = ((Boolean) a[0]) || ((Boolean) a[1]);
        for (int i = 2; i < a.length; i++) {
            flag |= (Boolean) a[i];
        }
        return flag;
    }

    /*
     * time
     */
    private static String timeNowMs(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "time.now_ms", 0));
        }
        return Long.toString(System.currentTimeMillis());
    }

    private static String timeTomorrowMs(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                errorMessage(invalidArgumentsNumber, "time.tomorrow_ms", 0));
        }
        return Long.toString(System.currentTimeMillis()+ 86400000);
    }

    private static String timeNowS(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "time.now_s", 0));
        }
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /*
     * json
     */
//    private static JsonPath getJsonPath(String jsonPath) {
//        return jsonPaths.computeIfAbsent(jsonPath, JsonPath::compile);
//    }

    private static String jsonRenderProp0(Object[] a, boolean withComma) {
        String key = (String) a[0], val = (String) a[1], ending = withComma ? "," : "";
        if (Objects.equals("null", val)) {
            return "\"" + key + "\": null" + ending;
        } else if (Objects.equals("", val)) {
            return "";
        } else {
            return "\"" + key + "\": \"" + val + "\"" + ending;
        }
    }

    private static String jsonEmptyMap(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "json.empty_map", 0));
        }
        return "{}";
    }

    private static String jsonRenderProp(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2, errorMessage(invalidArgumentsNumber, "json.render_prop", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop", 1, typeName(String.class)));
        }
        return jsonRenderProp0(a, true);
    }

    private static String jsonRenderProp2(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2, errorMessage(invalidArgumentsNumber, "json.render_prop2", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop2", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop2", 1, typeName(String.class)));
        }
        return jsonRenderProp0(a, false);
    }

    private static String jsonRenderPropAtLast(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2, errorMessage(invalidArgumentsNumber, "json.render_prop_at_last", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop_at_last", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "json.render_prop_at_last", 1, typeName(String.class)));
        }
        return jsonRenderProp0(a, false);
    }

    private static String jsonRenderPropJoin(Boolean debug, Object[] a) {
//        if (debug) {
//            checkArgument(a.length >= 1,
//                    errorMessage(invalidArgumentsNumberAtLeast, "str.join", 1));
//            checkArgument(a[0] instanceof String,
//                    errorMessage(invalidArgumentType, "str.join", 0, typeName(String.class)));
//        }
//        String delimiter = (String) a[0];
        String[] sa = Stream.of(a)
                .map(o -> (String) o)
                .filter(StringUtils::isNotBlank)
                .toArray(String[]::new);
        return String.join(",", sa);
    }

    private static String jsonValue(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1, errorMessage(invalidArgumentsNumber, "json.value", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.value", 0, typeName(String.class)));
        }
        String val = (String) a[0];
        if (Objects.equals("null", val)) {
            return val;
        }  else {
            return "\"" + val + "\"";
        }
    }

    private static String jsonDraw(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 2, errorMessage(invalidArgumentsNumber, "json.draw", 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.draw", 0, typeName(String.class)));
            checkArgument(a[1] instanceof String,
                    errorMessage(invalidArgumentType, "json.draw", 1, typeName(String.class)));
        }
        return getJsonPath((String) a[1]).read(a[0]);
    }

    private static String jsonStr(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "json.str", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "json.str", 0, typeName(String.class)));
        }
        return ((String) a[0]).replace("\n", "").replace("\"", "\\\"");
    }

    /*
     * txlog
     */
    private static String txlogValue(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1, errorMessage(invalidArgumentsNumber, "txlog.value", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "txlog.value", 0, typeName(String.class)));
        }
        String val = (String) a[0];
        if (Objects.equals("null", val)) {
            return "[null]";
        }  else {
            return val;
        }
    }

    /*
     * base64
     */
    private static String base64Enc(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "base64.enc", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "base64.enc", 0, typeName(String.class)));
        }
//        return Base64Utils.encodeToString(((String) a[0]).getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(((String) a[0]).getBytes(StandardCharsets.UTF_8));
    }

    private static String base64Dec(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "base64.dec", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "base64.dec", 0, typeName(String.class)));
        }
//        return new String(Base64Utils.decodeFromString((String) a[0]));
        return new String(Base64.getDecoder().decode((String) a[0]));
    }

    private static String base64EncUrlSafe(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "base64.enc_urlsafe", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "base64.enc_urlsafe", 0, typeName(String.class)));
        }
//        return Base64Utils.encodeToUrlSafeString(((String) a[0]).getBytes(StandardCharsets.UTF_8));
        return Base64.getUrlEncoder().encodeToString(((String) a[0]).getBytes(StandardCharsets.UTF_8));
    }

    private static String base64DecUrlSafe(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "base64.dec_urlsafe", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "base64.dec_urlsafe", 0, typeName(String.class)));
        }
//        return new String(Base64Utils.decodeFromUrlSafeString((String) a[0]));
        return new String(Base64.getUrlDecoder().decode((String) a[0]));
    }

    private static String urlEnc(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "url.enc", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "url.enc", 0, typeName(String.class)));
        }
        try {
            return URLEncoder.encode((String) a[0], StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static String urlDec(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "url.dec", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "url.dec", 0, typeName(String.class)));
        }
        try {
            return URLDecoder.decode((String) a[0], StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }

    /*
     * resp
     */
    private static String respStatus(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "resp.status", 0));
        }
        return Integer.toString(ZwsVar.getResponse().getStatusCodeValue());
    }

    private static String respDrawBody(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "resp.draw_body", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "resp.draw_body", 0, typeName(String.class)));
        }
        return getJsonPath((String) a[0]).read(ZwsVar.getResponseBody());
    }

    public static String respDrawHeader(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length >= 1 && a.length < 3,
                    errorMessage(invalidArgumentsNumberRange, "resp.draw_header", 1, 2));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "resp.draw_header", 0, typeName(String.class)));
            if (a.length == 2) {
                checkArgument(a[1] instanceof String,
                        errorMessage(invalidArgumentType, "resp.draw_header", 1, typeName(String.class)));
            }
        }
        List<String> values = ZwsVar.getResponseHeaders().get((String) a[0]);
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        String value = values.get(0);
        if (a.length > 1) {
            return getJsonPath((String) a[1]).read(value);
        } else {
            return value;
        }
    }

    public static Object get(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 1,
                    errorMessage(invalidArgumentsNumber, "get", 1));
            checkArgument(a[0] instanceof String,
                    errorMessage(invalidArgumentType, "get", 0, typeName(String.class)));
        }
        return ZwsVar.get((String) a[0]);
    }

    public static Object uuid(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "uuid", 0));
        }
        return UUID.randomUUID().toString();
    }

    public static Object uuidNoDash(Boolean debug, Object[] a) {
        if (debug) {
            checkArgument(a.length == 0,
                    errorMessage(invalidArgumentsNumber, "uuid", 0));
        }
        return UUID.randomUUID().toString().replace("-", "");
    }
}
